#ifndef __cameraH__
#define __cameraH__

#include "game.h"
#include "graphics.h"
#include "dynamic.h"

namespace WtfEngine
{
	class IPerspective: public virtual IGcObject
	{
		public:
			/// Set up the projection to that of this camera
			virtual void EnterPerspective(const IScreen::tRef& pScreen) const = 0;
			/// Return the projection to the previous state
			virtual void LeavePerspective() const = 0;

			GC_INTERFACE(IPerspective);
	};

	/**
	 * Generic Camera interface
	 **/
	class ICamera : public virtual IPerspective
	{
	public:
		virtual tVector getPosition() = 0;
		virtual tVector getDirection() = 0;
		virtual void getDirection(tVector& dir, tVector& up) = 0;
		
		/// Converts a pixel coordinate into a ray based upon the camera's projection.
		virtual tRay PixelToRay(const IScreen::tRef& pScreen, const iVector2D& vPixel) const = 0;

		GC_INTERFACE(ICamera);
	};
	
	/**
	 * Basic camera
	 **/
	class Camera : public virtual ICamera, public GcObject
	{
		protected:
			tVector mPosition, mDirection, mUpDirection;
		
		public:
			Camera(const tVector& pos, const tVector& dir, const tVector& up):
				GcObject(), mPosition(pos), mDirection(dir), mUpDirection(up) {};
			
			void EnterPerspective(const IScreen::tRef& pScreen) const;
			void LeavePerspective() const;
		
			tRay PixelToRay(const IScreen::tRef& pScreen, const iVector2D& vPixel) const;

			tVector getPosition() {
				return mPosition;
			};
			tVector getDirection() {
				return (mDirection - mPosition);
			};
			tVector getUpDirection() {
				return mUpDirection;
			};
			void getDirection(tVector& dir, tVector& up){
				dir = getDirection();
				up = getUpDirection();
			};
			
			void setPosition(const tVector& pos) {
				mPosition = pos;
			};
			void setDirection(const tVector& dir) {
				mDirection = dir;
			};
			void setUpDirection(const tVector& dir) {
				mUpDirection = dir;
			};
			void setDirection(const tVector& dir, const tVector& up) {
				mDirection = dir; mUpDirection = up;
			};
			
			GC_AUTOSIZE(Camera);
	};
	
	/**
	 * A movable camera, with controls simalar to FPS
	 **/
	class MovableCamera : public Camera
	{
		protected:
			void OnMoveFwd();
			void OnMoveBack();
			void OnMoveLeft();
			void OnMoveRight();
			void OnLook();
			
		public:
			MovableCamera(const tVector& pos, const tVector& dir, const tVector& up):
				Camera(pos, dir, up) {};
			
			void BindToKeyboardAndMouse();
			
			GC_AUTOSIZE(MovableCamera);
	};


	/**
	 * A camera which can be moved around the scene by edge scrolling or
	 * holding the right mouse button. Zoom can be adjusted with the mouse
	 * wheel, and the view angle can be adjusted by holding the middle
	 * mouse button.
	 **/
	class PannableCamera: public virtual IDynamic, public Camera {
	public:
		typedef struct PannableCameraOptions {
			tScalar		fMaxDistance, fMinDistance;
			tScalar		fZoomSpeed, fScrollSpeed;
			bool		bEdgeScrolling;

			PannableCameraOptions():
				fMaxDistance(200.0), fMinDistance(15.0), fZoomSpeed(10.0), fScrollSpeed(2.0),
					bEdgeScrolling(false) {};
		} tCreationOptions;

	private:
		PannableCameraOptions	mOptions;
		tPosition				mvFocalPoint;
		tScalar					mfDesiredZoom;
		tPosition				mvDesiredPosition;
		bool					mbIsScrolling, mbIsPanning;
		bool					mbHasScrolled, mbHasPanned;
		

	protected:
		void OnMouseMove();
		void OnMouseDown();
		void OnMouseUp();

		bool UpdateFocalPoint();

	public:
		PannableCamera(const tCreationOptions& opts, const tPosition& pos, const tDirection& direction);

		void Zoom(tScalar amt);
		void Scroll(const fVector2D& amt);

		void Update();

		void BindToKeyboardAndMouse();

		inline tScalar getZoom() const {
			return mfDesiredZoom;
		};

		GC_AUTOSIZE(PannableCamera);
	};
};


#endif
